{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center>Автор материала: Александровская Вера (@shlur), DS в Lamoda."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Рисуем интерактивные карты с Folium"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В питоне есть множество библиотек, с помощью которых можно рисовать и анализировать пространственную информацию (spatial analysis).\n",
    "\n",
    "Вот некоторые из них:\n",
    "\n",
    "* folium\n",
    "* gmaps\n",
    "* basemap\n",
    "* cartopy\n",
    "* geoplotlib\n",
    "\n",
    "\n",
    "В этом туториале пойдет речь о библиотеке Folium (https://github.com/python-visualization/folium), которая представляет собой питон-обертку над JS библиотекой Leaflet.\n",
    "\n",
    "\"Manipulate your data in Python, then visualize it in on a Leaflet map via Folium.\" (c) github\n",
    "\n",
    "Большим плюсом по сравнению с другими библиотеками является интерактивность. Карты можно зумить, исследовать, кликать на маркеры, создать сложные типы визуализации.\n",
    "Минусом является производительность. Создание карты с большим количеством точек может составлять минуты.\n",
    "\n",
    "К сожалению, информация по работе с библиотекой раскидана по разным сайтам и туториалам. В официальной документации информации очень мало, а в русскоязычном интернете вообще ничего нет.\n",
    "\n",
    "Будем исследовать датасет с пабами Москвы =). \n",
    "\n",
    "Датасет в виде списка пабов Москвы с их координатами выгружен с http://openstreetmap.ru"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Подготовка данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "import folium\n",
    "import pandas as pd\n",
    "import requests\n",
    "\n",
    "with open('../../data/pubs.json') as json_data:\n",
    "    d = json.load(json_data)\n",
    "    \n",
    "columns = ['lat', 'lon', 'name_ru', 'opening_hours', 'website']\n",
    "index = range(0, len(d[\"data\"]))\n",
    "pubs = pd.DataFrame(columns = columns, index = index)\n",
    "\n",
    "for i in range(0, len(d[\"data\"])):\n",
    "    pubs['lat'][i] = d[\"data\"][i][\"lat\"]\n",
    "    pubs['lon'][i] = d[\"data\"][i][\"lon\"]\n",
    "    pubs['opening_hours'].iloc[i] = d[\"data\"][i][\"opening_hours\"]\n",
    "    pubs['website'][i] = d[\"data\"][i][\"website\"]\n",
    "    pubs['name_ru'][i] = d[\"data\"][i][\"name_ru\"]    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pubs.head(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Для центрирования карт я выбрала центральную точку Москвы\n",
    "\n",
    "kremlin = [55.750730, 37.617322]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dot density map"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Самый простой вариант анализа - отобразить данные как точки (или маркеры) на карте. В popup (выноску) положим название заведения и часы работы.\n",
    "\n",
    "Карта инициализируется с помощью синтаксиса `map = folium.Map(location=kremlin, zoom_start=11)`.\n",
    "\n",
    "Добавляем маркеры `folium.Marker().add_to(map)`.\n",
    "\n",
    "Различные типы маркеров задаются функциями:\n",
    "\n",
    "- Marker (использую для визуализации ниже)\n",
    "- RegularPolygonMarker\n",
    "- CircleMarker\n",
    "- PolygonMarker\n",
    "\n",
    "Возможные атрибуты: \n",
    "\n",
    "- color\n",
    "- fill_color\n",
    "- weight\n",
    "- radius\n",
    "- number_of_sides (для RegularPolygonMarker)\n",
    "\n",
    "Так же в выноску можно передавать график vincent (https://github.com/wrobstory/vincent) с помощью синтаксиса: \n",
    "\n",
    "`folium.Popup().add_child(folium.Vega())`\n",
    "\n",
    "`tiles` - источник тайлов карт. Я обычно использую openstreetmaps - они идут по умолчанию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pubs_map = folium.Map(location=kremlin, zoom_start=11)\n",
    "\n",
    "for i in range(0, len(pubs)):\n",
    "    folium.Marker([pubs['lat'][i], pubs['lon'][i]], popup = str(pubs['name_ru'][i]) + \": \" \n",
    "                  + str(pubs['opening_hours'][i])).add_to(pubs_map)\n",
    "    \n",
    "pubs_map"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cluster marker - раскраска в зависимости от плотности точек. Близкие точки сливаются в один маркер.\n",
    "\n",
    "Судя по данным, наибольшая плотность пабов в Москве - на серево-востоке от Кремля, в районе Чистых прудов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from folium import features\n",
    "\n",
    "pubs_map = folium.Map(location=kremlin, zoom_start=12)\n",
    "mc = features.MarkerCluster()\n",
    "\n",
    "for i in range(0, len(pubs)):\n",
    "    mk = features.Marker([pubs['lat'][i], pubs['lon'][i]])\n",
    "    mc.add_child(mk)\n",
    "    \n",
    "pubs_map.add_child(mc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Heatmap"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим Heatmap распределения пабов по Москве"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "import numpy as np\n",
    "from folium import plugins\n",
    "\n",
    "pubs_map = folium.Map(location=kremlin, zoom_start=10)\n",
    "\n",
    "data = [[x[0], x[1], 1] for x in np.array(pubs[['lat', 'lon']])]\n",
    "\n",
    "HeatMap(data, radius = 20).add_to(pubs_map)\n",
    "pubs_map"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Линии"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавим на карту линии, соединяющую локации. Она создаётся с помощью folium.PolyLine, который принимает координаты соединяемых точек, и настраивается параметрами, схожими с параметрами маркеров.\n",
    "\n",
    "Здесь мы выбрали и соединили 4 заведения в Сокольниках:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sololniki = [55.791981, 37.664456]\n",
    "\n",
    "pubs_map_sokolniki = folium.Map(location=sololniki, zoom_start=13)\n",
    "path  = []\n",
    "for i in [100, 101, 102, 103]:\n",
    "    folium.Marker([pubs['lat'][i], pubs['lon'][i]], popup = str(pubs['name_ru'][i]) + \": \" \n",
    "                  + str(pubs['opening_hours'][i])).add_to(pubs_map_sokolniki)\n",
    "    path.append([[pubs['lat'][i], pubs['lon'][i]], [pubs['lat'][i+1], pubs['lon'][i+1]]])\n",
    "\n",
    "folium.PolyLine(path[0:3], color='blue', weight=4, opacity=0.7, popup=str(i)).add_to(pubs_map_sokolniki)\n",
    "pubs_map_sokolniki   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Найдем и отобразим кратчайший путь через пабы Москвы :)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В заключении туториала, приведу пример отображения кратчайшего пути через пабы Москвы. Идея нагло украдена отсюда: http://www.math.uwaterloo.ca/tsp/pubs/\n",
    "\n",
    "Для нахождения кратчайшего пути использую библиотеку Google or-tools, которая включает в себя алгоритмы решения задач нахождения маршрута. Работа с ней это тема для отдельного туториала, поэтому загружаю найденное решение из внешнего файла.\n",
    "Пути между заведениями тут уже строятся не отрезками, соединяющими пары пабов, а следуют кратчайшему маршруту из паба в паб, подобранному с помощью http://project-osrm.org"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "# инициализируем карту\n",
    "map = folium.Map(location=kremlin, zoom_start=14)\n",
    "\n",
    "# грузим файл с данными о маршрутах\n",
    "file = open('../../data/tutorial_data.pickle', 'rb')\n",
    "routes = pickle.load(file,encoding='latin1')\n",
    "file.close()\n",
    "\n",
    "# Рисуем большой полилайн полного маршрута...\n",
    "for path in routes['paths']:\n",
    "    folium.PolyLine(path, color='black', weight=3, opacity=0.8).add_to(map)\n",
    "# ...и маркеры пабов\n",
    "for point in routes['points']:\n",
    "    folium.Marker(point).add_to(map)\n",
    "\n",
    "map"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Have fun =)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
